Eine parametrische Kurve erlaubt geometrische Anfragen, die mit einer bloßen Formel nicht möglich wären:
e.position_at(0.5) # Punkt bei t = 0.5
e.tangent_at(0.5) # Tangentenvektor dort
e.length # Gesamtlänge
Eine Kurve im 3D-Raum als Funktion eines Parameters :
Der Tangentenvektor gibt Richtung und Geschwindigkeit entlang der Kurve an:
Die Bogenlänge misst die tatsächlich zurückgelegte Weglänge entlang der Kurve:
Bogenlängenparametrisierung ( als Parameter):
→ Tangentenvektor hat immer Länge 1
→ Geometrisch „natürliche“ Parametrisierung; rechnerisch aufwändig
Die Krümmung misst, wie stark sich die Tangentenrichtung mit der zurückgelegten Weglänge ändert:
→ Bogenlängenparametrisierung
Für beliebige Parametrisierung (via Kettenregel + Lagrange-Identität):
u: 0.0 0.25 0.5 0.75 1.0
●─────────●─────────●─────────●─────────●
C(0) C(½) C(1)
In build123d: position_at(t) mit liefert den Punkt, tangent_at(t) den Tangentenvektor.
Flächen sind Funktionen von zwei Parametern und :
Der Normalenvektor ergibt sich aus den Tangenten in - und -Richtung:
→ Vorzeichen bestimmt, welche Seite „außen“ ist (erinnert an Orientierung aus VL2)
Die Flächentypen (Ebene, Zylinder, NURBS-Flächen, …) sind Thema von Vorlesung 4.
Nehmen Sie den Zylinder aus Aufgabe 1.1. Selektieren Sie eine Kreiskante und berechnen Sie position_at für .
Hinweise: .filter_by(bd.GeomType.CIRCLE)[0], .position_at(t), (p2 - p1).length
Berechnen Sie für dieselbe Kreiskante tangent_at(t) bei .
Hinweise: .tangent_at(t), Vector(0, 0, z), .dot(), .normalized()
Erstellen Sie eine Ellipse und berechnen Sie 8 gleichmäßig verteilte Parameterpunkte:
ellipse = bd.Edge.make_ellipse(x_radius=30, y_radius=15)
punkte = [ellipse.position_at(i / 8) for i in range(8)]
Hinweise: (p2 - p1).length, Folie „Parametrische Kurven"
Drei Grundobjekte für die Arbeit im Raum:
| Klasse | Bedeutung | Beispiel |
|---|---|---|
Vector |
Punkt oder freier Vektor | Vector(1, 2, 3) |
Axis |
Ursprung + Richtung (Achse) | Axis((0,0,0), (0,0,1)) |
Plane |
Ebene: Ursprung + Orientierung | Plane.XY |
Auf Vector sind die üblichen Operationen definiert: Addition, Skalierung, Länge (v.length), Normierung, Skalarprodukt (v.dot(w)), Kreuzprodukt (v.cross(w)).
Vordefinierte Achsen: Axis.X, Axis.Y, Axis.Z (Weltkoordinatensystem)
Vordefinierte Ebenen:
| Ebene | Normalenrichtung | Verwendung |
|---|---|---|
Plane.XY |
-Achse | Standard-Skizzierebene |
Plane.XZ |
-Achse | Frontansicht |
Plane.YZ |
-Achse | Seitenansicht |
Benutzerdefiniert:
Axis((5, 3, 0), (0, 0, 1)) – Achse durch beliebigen PunktPlane(origin=(0,0,10), z_dir=(1,0,0)) – Ebene mit beliebiger NormalenJedes Objekt hat eine Location – seine Position und Orientierung im Weltkoordinatensystem.
Eine Location kodiert:
Location ist das lokale Koordinatensystem eines Objekts relativ zur Welt.
Kann aus einem Punkt (Location((20, 10, 5))) oder aus einer Ebene (Location(Plane.XZ)) erzeugt werden.
| Operation | Methode |
|---|---|
| Verschiebung | obj.move(Location((dx, dy, dz))) |
| Drehung | obj.rotate(Axis.Z, winkel_grad) |
| Spiegelung | obj.mirror(Plane.XZ) |
| Skalierung | obj.scale(faktor) |
Wichtig: Jede Transformation liefert ein neues Objekt – das Original bleibt unverändert.
Transformationen lassen sich verketten:
zyl.rotate(Axis.Z, 30).move(Location((20, 0, 0)))
Eine Drehung im 3D-Raum ist durch Achse + Winkel vollständig beschrieben:
Intern wird daraus eine Rotationsmatrix berechnet, die auf jeden Punkt angewendet wird:
# Drehung um die Z-Achse, 45°
zyl.rotate(Axis.Z, 45)
# Drehung um eine beliebige Achse durch Punkt (5, 0, 0)
zyl.rotate(Axis((5, 0, 0), (0, 0, 1)), 30)
Translationen kommutieren – die Reihenfolge ist egal:
Rotationen kommutieren nicht – die Reihenfolge ist entscheidend:
Rotation + Translation kommutieren ebenfalls nicht:
→ Bei verketteten Transformationen immer auf die Reihenfolge achten.
Ein besonders nützliches Muster: Konstruktionsebene direkt aus einer vorhandenen Fläche ableiten.
ebene = Plane(oberseite) # Ursprung + Orientierung der Fläche
Die Ebene übernimmt automatisch den Flächenmittelpunkt als Ursprung und die Flächennormale als -Richtung.
Anwendung: Bohrung senkrecht auf einer schrägen Fläche – die Skizze wird auf der Fläche platziert, unabhängig von deren Lage im Raum.
Erstellen Sie einen Zylinder (radius=10, height=20) und führen Sie die gleichen Operationen in unterschiedlicher Reihenfolge durch:
center()) jeweils?Hinweise: .rotate(Axis.Z, winkel), .move(Location((dx, dy, dz))), .center()
Erstellen Sie einen Quader (Box(40, 30, 10)) und leiten Sie eine Konstruktionsebene von der Oberseite ab. Platzieren Sie darauf einen Zylinder (radius=8, height=15).
Plane(oberseite) direkt Plane.XY.offset(5) verwendet hätten?Hinweise: .faces().sort_by(Axis.Z).last, Plane(face), ebene * bd.Cylinder(...), + oder fuse
Bauen Sie eine asymmetrische Form aus zwei Quadern (sie sollen nicht spiegelsymmetrisch sein). Spiegeln Sie die Form an Plane.YZ und verbinden Sie Original und Spiegelbild.
Hinweise: .mirror(Plane.YZ), bd.Pos(x, y) * ..., +
Exakt durch eine geschlossene Formel beschreibbar:
| Typ | geom_type |
Vorkommen | |
|---|---|---|---|
| Linie | LINE |
Kanten eines Quaders | |
| Kreis | CIRCLE |
Kanten eines Zylinders | |
| Ellipse | wie Kreis, aber | ELLIPSE |
Kegelschnitte |
Für Standardkörper (Box, Cylinder, Sphere, …) reichen analytische Kurven vollständig aus.
Analytische Kurven sind mathematisch unbegrenzt – eine Linie reicht von bis .
Eine Edge ist immer ein endliches Stück: Kurve + Parameterintervall
Geom_Line (unendlich): ────────────────────────────────
↑ ↑
u₁ u₂
Edge (getrimmt): ●─────────────●
→ Dasselbe Kreisobjekt steckt hinter einem Halbkreis-Edge wie hinter einem Viertelkreis –
nur das Intervall unterscheidet sie.
An den vier Scheitelpunkten:
| Stelle | ||
|---|---|---|
| Ende der großen Halbachse () | (flach) | |
| Ende der kleinen Halbachse () | (stark gebogen) |
→ Die Ellipse hat keine konstante Krümmung – im Gegensatz zum Kreis, und das entscheidet, wie Operationen wie Versatz oder Abrundung auf sie wirken.
Eine Versatzkurve entsteht, indem jeder Punkt der Originalkurve um eine konstante Distanz entlang des Normalenvektors verschoben wird:
Typische CAD-Anwendungen:
Kreis (Radius ): alle Normalen zeigen durch den Mittelpunkt → der Versatz verschiebt den Radius gleichmäßig.
→ wieder ein Kreis!
Grund: Konstante Krümmung → Normalen drehen gleichmäßig → Versatz ändert nur den Radius.
Ellipse (, ): Normalen drehen ungleichmäßig – die Krümmung variiert.
Die Versatzkurve lässt sich nicht als schreiben:
ellipse = bd.Edge.make_ellipse(x_radius=30, y_radius=15)
offset = ellipse.offset_2d(5)
for e in offset.edges():
print(e.geom_type) # → OFFSET
OCCT berechnet den Versatz numerisch als Offset-Kurve – keine analytische Ellipse mehr.
Bei konstanter Krümmung (Kreis) wirkt der Versatz überall gleich.
Bei variabler Krümmung (Ellipse) wirkt er ungleichmäßig:
Geometrische Operationen erzeugen häufig Kurven höherer Komplexität als die Eingabe —
selbst bei einfachen Ausgangskurven.
Daher brauchen CAD-Systeme eine Darstellung, die beliebige glatte Kurven beschreiben kann.
Erstellen Sie einen freistehenden Kreis und berechnen Sie den Versatz:
kreis = bd.Edge.make_circle(radius=20)
versatz = kreis.offset_2d(5)
geom_type haben die resultierenden Kanten? Was bedeutet das geometrisch?offset_2d einen Wire (mehrere Kanten) statt einer einzelnen Kante?Hinweise: .edges(), .geom_type, .length, (voller Kreis) oder (Halbkreis)
Erstellen Sie eine Ellipse (x_radius=30, y_radius=15) und versetzen Sie sie um 5.
geom_type haben die resultierenden Kanten? Unterschied zu 4.1?Hinweise: .offset_2d(d), .edges(), .geom_type
Edge trägt eine Kurve , Face trägt eine Fläche Vector, Axis, Plane, Location – räumliches Handwerkszeug; Transformationen erzeugen neue ObjekteErstellen Sie eine Ringscheibe mit 6 Bohrungen auf einem Lochkreis:
import math
scheibe = bd.Cylinder(radius=40, height=8) - bd.Cylinder(radius=12, height=8)
for winkel in range(0, 360, 60):
x = 28 * math.cos(math.radians(winkel))
y = 28 * math.sin(math.radians(winkel))
scheibe = scheibe - bd.Pos(x, y) * bd.Cylinder(radius=4, height=8)
radius=0.5.Hinweise: collections.Counter, .filter_by(bd.GeomType.CIRCLE), .radius,
.filter_by_position(Axis.Z, minimum=..., maximum=...), bd.fillet(..., radius=...)